package Ball (mkBall, Ball(..)) where

import ActionSeq
import Global
import Paddle
import Border
import Shape
import Color

interface Ball =
    shape :: Shape

    center :: YCoord
    dir :: Bool

    tick :: Action

col :: Color
col = cYellow

ballWidthC :: XSize
ballWidthC = fromInteger ballWidth

ballHeightC :: YSize
ballHeightC = fromInteger ballHeight

-- bounce top   bottom up/down      => up/down
-- bounce right left   right/left   => right/left
-- The result is the new direction
bounce :: Bool -> Bool -> Bool -> Bool
bounce False False False = False   -- no hit, going down
bounce False False True  = True    -- no hit, going up

bounce False True  False = False   -- bottom hit, going down (touching a corner)
bounce False True  True  = False   -- bottom hit, going up   (bouncing on a wall)

bounce True  False False = True    -- top hit, going down    (bouncing on a wall)
bounce True  False True  = True    -- top hit, going up      (touching a corner)

bounce True  True  False = False   -- both hit, going down   (touching a wall)
bounce True  True  True  = True    -- both hit, going up     (touching a wall)


mkBall:: (Add x 2 n) => Bit n -> Paddle -> Paddle -> Action -> Action -> Module Ball
mkBall rand paddleL paddleR lAct rAct =
  module

    centerR :: Reg YCoord <- mkRegU

    ball_x :: Reg XCoord <- mkReg (fromInteger (hSize `div` 2))
    ball_y :: Reg YCoord <- mkReg (fromInteger (vSize `div` 2))

    ball_x_r :: Reg XCoord <- mkRegU
    ball_y_b :: Reg YCoord <- mkRegU

    ball_vx :: Reg XCoord <- mkReg 7
    ball_vy :: Reg YCoord <- mkReg 1
--    ball_vy2 :: Reg YCoord <- mkRegU
--    ball_vy3 :: Reg YCoord <- mkRegU
    -- The following uses only one register, but more muxes and logic
    let ball_vy2 = asReg ball_vy
        ball_vy3 = asReg ball_vy

    ballRect :: Shape <- mkRectangle ball_x ballWidthC ball_y ballHeightC col

    change_y :: Reg YCoord <- mkReg 0

    ball_dx :: Reg Bool <- mkRegU  -- True == Right
    ball_dy :: Reg Bool <- mkRegU  -- True == Up  (Note, up is increasing y coordinates)

    -- random velocity change
    let r2 :: Bit 2
        r2 = Prelude.truncate rand
        randV = case r2 of
                0b00 -> 1
                0b01 -> negate 1
                _    -> 0

    -- steps in updating the ball
    let step1 =
          action
            -- Change direction if needed
            ball_vx  := if posX ball_vx == ball_dx then ball_vx else negate ball_vx
            ball_vy2 := if posY ball_vy == ball_dy then ball_vy else negate ball_vy

        step2 =
          action
            -- Update speed
            ball_vy3 := ball_vy2 + shiftY change_y 3 + if change_y /= 0 then randV else 0

        step3 =
          action
            -- Limit speed
            ball_vy := limitY ball_vy3

        step4 =
          action
            -- Move ball
            ball_x := ball_x + ball_vx
            ball_y := ball_y + ball_vy

        step5 =
          action
            -- Update right and bottom coord
            ball_x_r := ball_x + ballWidthC
            ball_y_b := ball_y + ballHeightC

    -- break updating into several steps to meet timing
    updateBalls :: ActionSeq <- actionSeq (step1 |> step2 |> step3 |> step4 |> step5)

    interface
        shape = ballRect

        center = centerR

        dir = ball_dx

        tick =
          action
            let (a00,a01,a10,a11,apos) = paddleL.inside ball_x ball_x_r ball_y ball_y_b ballHeightC
                (b00,b01,b10,b11,bpos) = paddleR.inside ball_x ball_x_r ball_y ball_y_b ballHeightC

                -- New direction for Y
                diry = bounce (b00 || b10) (b01 || b11) $
                       bounce (a00 || a10) (a01 || a11) $
                       (ball_y <= fromInteger (yMax - ballHeight) &&  -- down if to large
                       ((ball_y < fromInteger yMin) ||                  -- up if to small
                        posY ball_vy))


                -- New direction for X
                -- Need to know which wall for counting points
                -- Need to know where on the paddle for changing vy

                hitWallR = ball_x > fromInteger (xMax - ballWidth)
                hitWallL = ball_x < fromInteger xMin

                dirx = bounce (a00 || a01) (a10 || a11) $
                       bounce (b00 || b01) (b10 || b11) $
                       ( not hitWallR && ( hitWallL || posX ball_vx) )

            change_y :=      if (a00 || a01) /= (a10 || a11) then apos
                        else if (b00 || b01) /= (b10 || b11) then bpos
                        else 0

            if hitWallR then rAct else noAction
            if hitWallL then lAct else noAction

            ball_dx := dirx
            ball_dy := diry

            updateBalls.start

    rules

       "MoveBall":
        when True ==>
          action
            centerR := ball_y + fromInteger (ballHeight `div` 2)
